/*
 * wffs mkfs
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "wffs.h"
#include "wffs_format.h"
#include "index_tree.h"
#include "extent_tree.h"
#include "btree_algo.h"
#include "nand.h"
#include "wffs_super.h"

int wffs_insert_block_group(struct wffs_root *root, struct wffs_key key,
							struct wffs_block_group_entry item)
	{

	}
int wffs_make_block_groups(struct flash_descriptor *nand_flash)
	{
	struct wffs_fs_info               *fs_info;
	struct wffs_root                  *extent_root;
	struct wffs_raw_key               key;
	struct wffs_block_group_item_body item;

	fs_info = wffs_read_fs_info(nand_flash);
	extent_root = fs_info->extent_root;

	while (all block groups)
		{
		wffs_insert_block_group(extent_root, &key, &item);
		}
	return 0;
	}

static int wffs_calculate_bg_size(uint64_t flash_size,
								  uint32_t flash_pg_size,
								  uint32_t flash_eb_size)
	{
	uint32_t bg_size;

	bg_size = flash_size / 8192;

	if (bg_size < flash_eb_size)			 /*for small flash*/
		{
		return(8 * flash_eb_size);
		}
	else if (bg_size / flash_eb_size > 1024) /*for big flash*/
		{
		return(1024 * flash_eb_size);
		}
	else
		return(bg_size);					 /*for normal flash*/
	}

/* 
 *                      |-0-|-1-|-2-|-3|                            |index|ext |
 * |R|S|2 sb head blocks|sb link blocks|float super|block group maps|root |root| 
 * |-----------------------------free space------------------------------------| 
 * |-----------------------------free space------------------------------------|
 * |-----------------------------free space------------------------------------| 
 * |-----------------------------free space------------------------------------| 
 * |-----------------------------free space------------------------------------| 
 * |------some reserved bg-------| 
 *  
 * R - reserved for MBR or PBR 
 * S - static super block which contains data never change 
 *  
 * XXX we assume there are no bad blocks on format area 
 */

static int wffs_format_internal(struct flash_descriptor *nand_flash,
								uint64_t flash_size,
								uint32_t flash_pg_size,
								uint32_t flash_eb_size,
								uint32_t flash_bg_size,
								uint32_t flash_block_size)
	{
	uint64_t                            retlen;
	uint64_t                            float_super_start;
	uint64_t                            index_root_offset;
	uint32_t                            flash_bg_number;
	uint32_t                            bg_map_size;
	struct wffs_raw_sb_head             sh;
	struct wffs_raw_sb_link             sl;
	struct wffs_static_raw_super_block  static_sb;
	struct wffs_float_raw_super_block   sb;
	struct wffs_raw_key                 key;
	struct wffs_raw_block_header        b_header;
	struct wffs_inode_item_body         inode_body;
	struct wffs_dir_item_body           dir_item_body;
	struct wffs_raw_item_header         item_header;
	struct wffs_extent_item_body        extent_item_body;
	void *buf = NULL;
	uint64_t M, N;
	int m, link_block_number, first_block_number, i;
	int err = 0;
	int cp_offset_left = 0;
	int cp_offset_right = flash_block_size
						  - sizeof(struct wffs_inode_item_body);

	buf = malloc(flash_block_size);
	if (NULL == buf)
		{
		err = -1;
		goto out;
		}
	memset(buf, 0, flash_block_size);

	/*erase the whole flash*/
	nand_flash->erase(0, flash_size);

	/* 
	 * calculate indirect level m for super blocks 
	 *         (M-3)/4
	 * m >= logN  
	 *  
	 * M:all blocs number 
	 * N:page number in every block 
	 *  
	 * m = 0, just use sb head blocks to record super(never happen)
	 * m = 1, no link blocks 
	 * m = 2, two head blocks, one link block, one superblock 
	 *  
	 * link block number = m - 1; 
	 */
	M = flash_size / flash_block_size;
	N = flash_block_size / flash_pg_size;

	m = log((M - 3)/4) / log(N) + 1;
	link_block_number = m - 1;

	/*calculate others*/
	flash_bg_number = flash_size / flash_bg_size;
	bg_map_size     = (flash_bg_number * flash_eb_size + flash_pg_size)
					  &~ flash_pg_size;

	first_block_number         = WFFS_SPACE_RESERVED_IN_HEAD / flash_block_size;

	/*init static raw sb*/
	static_sb.flash_pg_size    = flash_pg_size;
	static_sb.flash_eb_size    = flash_eb_size;
	static_sb.flash_size       = flash_size;

	static_sb.flash_block_size = flash_block_size;
	static_sb.flash_bg_size    = flash_bg_size;

	static_sb.bg_map_offset    = first_block_number + 3 + m;
	static_sb.sb_link_level    = m;

	strncpy((char *)&static_sb.magic, WFFS_MAGIC, sizeof(static_sb.magic));

	nand_flash->write(WFFS_SPACE_RESERVED_IN_HEAD,
					  sizeof(struct wffs_static_raw_super_block),
					  &retlen, (const uint8_t *)&static_sb);

	/*init sb head*/
	sh.link = first_block_number + 3;
	strncpy((char *)&sh.magic, WFFS_MAGIC, sizeof(sh.magic));
	sh.version = 0;
	nand_flash->write(WFFS_SPACE_RESERVED_IN_HEAD + flash_block_size,
					  sizeof(struct wffs_raw_sb_head),
					  &retlen, (const uint8_t *)&sh);

	/*init sb link*/
	sl.link = sh.link + 1;
	for (i = 0; i < link_block_number; i++)
		{
		sl.link = sh.link + i + 1;
		strncpy((char *)&sl.magic, WFFS_MAGIC, sizeof(sl.magic));
		sl.version = 0;
		nand_flash->write((sh.link + i) * flash_block_size,
						  sizeof(struct wffs_raw_sb_link),
						  &retlen, (const uint8_t *)&sl);
		}

	/* init float sb*/
	strncpy((char *)&sb.magic, WFFS_MAGIC, sizeof(sb.magic));

	float_super_start   = sl.link;

	sb.index_tree_root  = float_super_start + bg_map_size / flash_block_size;
	sb.extent_tree_root = sb.index_tree_root + 1;

	sb.used_blocks      = float_super_start + 3
						  + bg_map_size / flash_block_size
						  + flash_bg_size / flash_block_size 
						  * WFFS_RESERVED_BLOCK_GROUP_NR;
	sb.version          = 0;

	nand_flash->write(float_super_start * flash_block_size,
					  sizeof(struct wffs_float_raw_super_block),
					  &retlen, (const uint8_t *)&sb);

	/*
	 * init index tree root 
	 * the index tree root contains: 
	 * 0)block header
	 *  --wffs_block_header--
	 *  level      = 0;
	 *  nr_items   = 3;
	 *  
	 * 1)root dir's inode item 
	 *  key.id     = WFFS_ROOT_DIR_ID
	 *  key.type   = WFFS_INODE_KEY
	 *  key.offset = 0
	 *  
	 *  --wffs_inode_item_body--
	 *  nrblocks   = 1;?
	 *  mode       = S_IFDIR | S_IRWXU | S_IRWXG | S_IROTH
	 *  atime      =
	 *  mtime      =
	 *  ctime      =
	 *  
	 * 2)'.'  dir item 
	 *  key.id     = WFFS_ROOT_DIR_ID
	 *  key.type   = WFFS_DIR_ITEM_KEY
	 *  key.offset = 1
	 *  
	 *  --wffs_dir_item_body--
	 *  ino        = WFFS_ROOT_DIR_ID
	 *  name_len   = 1
	 *  "." follows
	 *  
	 * 3)'..' dir item 
	 *  key.id     = WFFS_ROOT_DIR_ID
	 *  key.type   = WFFS_DIR_ITEM_KEY
	 *  key.offset = 2
	 *  
	 *  --wffs_dir_item_body--
	 *  ino        = (ULL)-1
	 *  name_len   = 2
	 *  ".." follows
	 */

	/*
	 * block header
	 */
	b_header.level    = 0;
	b_header.nr_items = 3;

	/*from left to right*/
	memcpy(buf, (void *)&b_header, sizeof(struct wffs_raw_block_header));
	cp_offset_left += sizeof(struct wffs_raw_block_header);

	/*
	 * root dir inode key and inode item body
	 */
	key.id              = WFFS_ROOT_DIR_ID;
	key.type            = WFFS_INODE_KEY;
	key.offset          = 0;
	item_header.key     = key;
	item_header.offset  = cp_offset_right;
	item_header.size    = sizeof(struct wffs_inode_item_body);

	inode_body.size     = 0;
	inode_body.atime    = 0; /*not supported now*/
	inode_body.ctime    = 0;
	inode_body.mtime    = 0;
	inode_body.mode     = S_IFDIR | S_IRWXU | S_IRWXG | S_IROTH;
	inode_body.nrblocks = 0;

	/*form left to right XXX for gcc only*/
	memcpy(buf + cp_offset_left, (void *)&item_header,
		   sizeof(struct wffs_raw_item_header));
	cp_offset_left += sizeof(struct wffs_raw_item_header);

	/*from right to left XXX for gcc only*/
	memcpy(buf + cp_offset_right, (void *)&inode_body,
		   sizeof(struct wffs_inode_item_body));
	cp_offset_right -= (sizeof(struct wffs_dir_item_body) + strlen("."));

	/*
	 * key and body of dir item "."
	 */
	key.id                 = WFFS_ROOT_DIR_ID;
	key.type               = WFFS_DIR_ITEM_KEY;
	key.offset             = 1;

	item_header.key        = key;
	item_header.offset     = cp_offset_right;
	item_header.size       = sizeof(struct wffs_dir_item_body) + strlen(".");

	dir_item_body.ino      = WFFS_ROOT_DIR_ID;
	dir_item_body.name_len = 1;

	/*form left to right XXX for gcc only*/
	memcpy(buf + cp_offset_left, (void *)&item_header,
		   sizeof(struct wffs_raw_item_header));
	cp_offset_left += sizeof(struct wffs_raw_item_header);

	/*from right to left XXX for gcc only*/
	memcpy(buf + cp_offset_right, (void *)&dir_item_body,
		   sizeof(struct wffs_dir_item_body));
	*((char *)(buf + cp_offset_right + 1)) = '.';
	cp_offset_right -= (sizeof(struct wffs_dir_item_body) + strlen(".."));

	/*
	 * key and body of dir item ".."
	 */
	key.id                 = WFFS_ROOT_DIR_ID;
	key.type               = WFFS_DIR_ITEM_KEY;
	key.offset             = 2;

	item_header.key        = key;
	item_header.offset     = cp_offset_right;
	item_header.size       = sizeof(struct wffs_dir_item_body) + strlen("..");

	dir_item_body.ino      = WFFS_ROOT_DIR_ID;
	dir_item_body.name_len = 2;

	/*from left to right*/
	memcpy(buf + cp_offset_left, (void *)&item_header,
		   sizeof(struct wffs_raw_item_header));

	/*from right to left*/
	memcpy(buf + cp_offset_right, (void *)&dir_item_body,
		   sizeof(struct wffs_dir_item_body));
	*((char *)(buf + cp_offset_right + 1)) = '.';
	*((char *)(buf + cp_offset_right + 2)) = '.';

	index_root_offset = WFFS_SPACE_RESERVED_IN_HEAD + flash_block_size * (m + 3)
						+ bg_map_size;

	nand_flash->write(index_root_offset, flash_block_size, &retlen, buf);

	/*
	 * init extent tree root 
	 * the extent tree contains: one extent for formated blocks 
	 *  
	 * key.id     = 0 
	 * key.type   = WFFS_EXTENT_KEY 
	 * key.offset = flash_bg_size * 2 + flash_block_size * 2
	 *  
	 * --wffs_extent_item_body-- 
	 * flags = EXTENT_USED
	 */

	memset(buf, 0, flash_block_size);
	cp_offset_left  = 0;
	cp_offset_right = flash_block_size - sizeof(struct wffs_extent_item_body);

	/*
	 * block header 
	 */
	b_header.level    = 0;
	b_header.nr_items = 1;

	memcpy(buf, (void *)&b_header, sizeof(struct wffs_raw_block_header));
	cp_offset_left += sizeof(struct wffs_raw_block_header);

	/*
	 * key and body of extent item
	 */
	key.id                 = 0;
	key.type               = WFFS_EXTENT_KEY;
	key.offset             = index_root_offset + flash_block_size * 2;

	item_header.key        = key;
	item_header.offset     = cp_offset_right;
	item_header.size       = sizeof(struct wffs_extent_item_body);

	extent_item_body.flags = WFFS_EXTENT_USED;

	/*from left to right*/
	memcpy(buf + cp_offset_left, (void *)&item_header,
		   sizeof(struct wffs_raw_item_header));

	/*from right to left*/
	memcpy(buf + cp_offset_right, (void *)&extent_item_body,
		   sizeof(struct wffs_extent_item_body));

	nand_flash->write(index_root_offset + flash_block_size,
					  flash_block_size, &retlen, buf);

	out:     
	if (buf != NULL)
		{
		free(buf);
		}

	return(err);
	}

int wffs_format(struct flash_descriptor *nand_flash, uint64_t flash_size,
				uint32_t flash_pg_size, uint32_t flash_eb_size,
				uint32_t flash_bg_size)
	{

	int err;
	/*parameter check*/
	if (nand_flash->size != flash_size
		|| nand_flash->page_size != flash_pg_size
		|| nand_flash->erase_size != flash_eb_size)
		{
		printf("wrong flash parameter\n");
		return(-1);
		}

	if (flash_bg_size)
		{
		if (flash_bg_size < flash_eb_size || flash_bg_size % flash_bg_size)
			{
			printf("wrong block size parameter\n");
			return(-1);
			}

		}

	/*determine block group size*/
	if (0 == flash_bg_size)
		{
		nand_flash->bg_size = wffs_calculate_bg_size(flash_size,
													 flash_pg_size,
													 flash_eb_size);
		}
	else
		{
		nand_flash->bg_size = flash_bg_size;
		}

	/*XXX determinde block size*/
	nand_flash->block_size = 4 * 1024;

	/*format flash media as initial state*/
	err = wffs_format_internal(nand_flash, flash_size,
							   flash_pg_size, flash_eb_size,
							   nand_flash->bg_size, nand_flash->block_size);
	if (err)
		return(err);

	/*insert block group entry to extent tree*/
	err = wffs_make_block_groups(nand_flash);
	if (err)
		return(err);

	/*print flash and fs format information*/
	printf("flash size:%llu, page size:%u, erase bock size:%u\n",
		   flash_size, flash_pg_size, flash_eb_size);
	printf("block group size:%u, block group numbers:%llu\n",
		   nand_flash->bg_size, flash_size / nand_flash->bg_size);

	return(0);
	}
